#ifndef _DEARRAYBUFFER_HPP
#define _DEARRAYBUFFER_HPP
/*-------------------------------------------------------------------------
 * drawElements C++ Base Library
 * -----------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Array buffer
 *//*--------------------------------------------------------------------*/

#include "deDefs.hpp"
#include "deMemory.h"

#include <new>

namespace de
{
namespace detail
{

void* ArrayBuffer_AlignedMalloc (size_t numBytes, size_t alignment);
void ArrayBuffer_AlignedFree (void*);

} // detail

//! Array buffer self-test.
void ArrayBuffer_selfTest (void);

/*--------------------------------------------------------------------*//*!
 * \brief Contiguous array that does not initialize its elements.
 *//*--------------------------------------------------------------------*/
template <typename T, size_t Alignment = (sizeof(T) > 4 ? 4 : sizeof(T)), size_t Stride = sizeof(T)>
class ArrayBuffer
{
public:
	DE_STATIC_ASSERT(Stride >= sizeof(T));

					ArrayBuffer		(void) throw();
					ArrayBuffer		(size_t numElements);
					ArrayBuffer		(const T* ptr, size_t numElements);
					ArrayBuffer		(const ArrayBuffer& other);
					~ArrayBuffer	(void) throw();
	ArrayBuffer&	operator=		(const ArrayBuffer& other);

	void			clear			(void) throw();
	void			setStorage		(size_t numElements); // !< \note after a succesful call buffer contents are undefined
	void			swap			(ArrayBuffer& other) throw();
	size_t			size			(void) const throw();
	bool			empty			(void) const throw();

	T*				getElementPtr	(size_t elementNdx) throw();
	const T*		getElementPtr	(size_t elementNdx) const throw();
	void*			getPtr			(void) throw();
	const void*		getPtr			(void) const throw();

private:
	void*			m_ptr;
	size_t			m_cap;
} DE_WARN_UNUSED_TYPE;

template <typename T, size_t Alignment, size_t Stride>
ArrayBuffer<T,Alignment,Stride>::ArrayBuffer (void) throw()
	: m_ptr	(DE_NULL)
	, m_cap	(0)
{
}

template <typename T, size_t Alignment, size_t Stride>
ArrayBuffer<T,Alignment,Stride>::ArrayBuffer (size_t numElements)
	: m_ptr	(DE_NULL)
	, m_cap	(0)
{
	if (numElements)
	{
		// \note no need to allocate stride for the last element, sizeof(T) is enough. Also handles cases where sizeof(T) > Stride
		const size_t	storageSize	= (numElements - 1) * Stride + sizeof(T);
		void* const		ptr			= detail::ArrayBuffer_AlignedMalloc(storageSize, Alignment);

		if (!ptr)
			throw std::bad_alloc();

		m_ptr = ptr;
		m_cap = numElements;
	}
}

template <typename T, size_t Alignment, size_t Stride>
ArrayBuffer<T,Alignment,Stride>::ArrayBuffer (const T* ptr, size_t numElements)
	: m_ptr	(DE_NULL)
	, m_cap	(0)
{
	if (numElements)
	{
		// create new buffer of wanted size, copy to it, and swap to it
		ArrayBuffer<T,Alignment,Stride> tmp(numElements);

		if (Stride == sizeof(T))
		{
			// tightly packed
			const size_t storageSize = sizeof(T) * numElements;
			deMemcpy(tmp.m_ptr, ptr, (int)storageSize);
		}
		else
		{
			// sparsely packed
			for (size_t ndx = 0; ndx < numElements; ++ndx)
				*tmp.getElementPtr(ndx) = ptr[ndx];
		}

		swap(tmp);
	}
}

template <typename T, size_t Alignment, size_t Stride>
ArrayBuffer<T,Alignment,Stride>::ArrayBuffer (const ArrayBuffer<T,Alignment,Stride>& other)
	: m_ptr	(DE_NULL)
	, m_cap	(0)
{
	if (other.m_cap)
	{
		// copy to temporary and swap to it

		const size_t	storageSize =	(other.m_cap - 1) * Stride + sizeof(T);
		ArrayBuffer		tmp				(other.m_cap);

		deMemcpy(tmp.m_ptr, other.m_ptr, (int)storageSize);
		swap(tmp);
	}
}

template <typename T, size_t Alignment, size_t Stride>
ArrayBuffer<T,Alignment,Stride>::~ArrayBuffer (void) throw()
{
	clear();
}

template <typename T, size_t Alignment, size_t Stride>
ArrayBuffer<T,Alignment,Stride>& ArrayBuffer<T,Alignment,Stride>::operator= (const ArrayBuffer& other)
{
	ArrayBuffer copied(other);
	swap(copied);
	return *this;
}

template <typename T, size_t Alignment, size_t Stride>
void ArrayBuffer<T,Alignment,Stride>::clear (void) throw()
{
	detail::ArrayBuffer_AlignedFree(m_ptr);

	m_ptr = DE_NULL;
	m_cap = 0;
}

template <typename T, size_t Alignment, size_t Stride>
void ArrayBuffer<T,Alignment,Stride>::setStorage (size_t numElements)
{
	// create new buffer of the wanted size, swap to it
	ArrayBuffer<T,Alignment,Stride> newBuffer(numElements);
	swap(newBuffer);
}

template <typename T, size_t Alignment, size_t Stride>
void ArrayBuffer<T,Alignment,Stride>::swap (ArrayBuffer& other) throw()
{
	void* const		otherPtr = other.m_ptr;
	const size_t	otherCap = other.m_cap;

	other.m_ptr = m_ptr;
	other.m_cap = m_cap;
	m_ptr		= otherPtr;
	m_cap		= otherCap;
}

template <typename T, size_t Alignment, size_t Stride>
size_t ArrayBuffer<T,Alignment,Stride>::size (void) const throw()
{
	return m_cap;
}

template <typename T, size_t Alignment, size_t Stride>
bool ArrayBuffer<T,Alignment,Stride>::empty (void) const throw()
{
	return size() == 0;
}

template <typename T, size_t Alignment, size_t Stride>
T* ArrayBuffer<T,Alignment,Stride>::getElementPtr (size_t elementNdx) throw()
{
	return (T*)(((deUint8*)m_ptr) + Stride * elementNdx);
}

template <typename T, size_t Alignment, size_t Stride>
const T* ArrayBuffer<T,Alignment,Stride>::getElementPtr (size_t elementNdx) const throw()
{
	return (T*)(((deUint8*)m_ptr) + Stride * elementNdx);
}

template <typename T, size_t Alignment, size_t Stride>
void* ArrayBuffer<T,Alignment,Stride>::getPtr (void) throw()
{
	return m_ptr;
}

template <typename T, size_t Alignment, size_t Stride>
const void* ArrayBuffer<T,Alignment,Stride>::getPtr (void) const throw()
{
	return m_ptr;
}

} // de

#endif // _DEARRAYBUFFER_HPP
